/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package org.atomojo.tools.sync;

import java.io.File;
import java.net.URI;
import java.util.Set;
import java.util.TreeSet;
import java.util.UUID;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.atomojo.app.client.Entry;
import org.atomojo.app.client.EntryClient;
import org.atomojo.app.client.FeedClient;
import org.atomojo.app.client.FeedDestination;
import org.atomojo.app.client.Link;
import org.atomojo.app.client.StatusException;
import org.infoset.xml.Attribute;
import org.infoset.xml.Document;
import org.infoset.xml.Element;
import org.infoset.xml.Name;
import org.restlet.Client;
import org.restlet.Request;
import org.restlet.Response;
import org.restlet.data.ChallengeResponse;
import org.restlet.data.ChallengeScheme;
import org.restlet.data.MediaType;
import org.restlet.data.Method;
import org.restlet.data.Reference;
import org.restlet.data.Status;
import org.restlet.representation.Representation;

/**
 *
 * @author alex
 */
public class FeedSynchronizer implements Runnable {

   public static final URI ATOM_NAMESPACE = URI.create("http://www.w3.org/2005/Atom");
   public static final Name CONTENT_NAME = Name.create(ATOM_NAMESPACE,"content");
   
   Logger log;
   Link source;
   Link target;
   boolean additive;
   int errorCount;
   public FeedSynchronizer(Link source,Link target) {
      this.source = source;
      this.target = target;
      this.additive = true;
      this.log = Logger.getLogger(FeedSynchronizer.class.getName());
   }
   
   public void setAdditive(boolean flag)
   {
      this.additive = flag;
   }
   
   public void setLogger(Logger log)
   {
      this.log = log;
   }
   
   public int getErrorCount() {
      return errorCount;
   }
   
   public void run() {
      errorCount = 0;
      final FeedClient sourceClient = new FeedClient(source.getLink());
      sourceClient.setIdentity(source.getIdentity());
      
      final FeedClient targetClient = new FeedClient(target.getLink());
      targetClient.setIdentity(target.getIdentity());
      
      try {
         Response response = sourceClient.get(new FeedDestination() {
            Set<UUID> entries = additive ? null : new TreeSet<UUID>();
            boolean ok = true;
            public void onFeed(Document feedDoc) {
               URI baseURI = feedDoc.getBaseURI();
               // Make sure to remove the xml:base on the feed element for storage;
               feedDoc.getDocumentElement().getAttributes().remove(Attribute.XML_BASE);

               if (!targetClient.exists()) {
                  log.info("Creating target feed "+targetClient.getLocation());
                  Status status = targetClient.create(feedDoc);
                  if (!status.isSuccess()) {
                     log.severe("Cannot create target feed, status="+status.getCode());
                     errorCount++;
                     ok = false;
                  }
               }
            }
            public void onEntry(Document entryDoc) {
               if (!ok) {
                  return;
               }
               Entry entry = new Entry(entryDoc);
               entry.index();

               UUID entryId = null;
               try {
                  entryId = UUID.fromString(entry.getId().substring(9));
               } catch (IllegalArgumentException ex) {
                  log.severe("Ignoring entry with bad UUID: "+entry.getId());
                  errorCount++;
                  return;
               }
               log.info("Entry: "+entryId);
               EntryClient entryClient = new EntryClient(new Reference(targetClient.getEntryLocation(entryId).toString()),target.getIdentity());
               String src = null;
               Element content = entryDoc.getDocumentElement().getFirstElementNamed(CONTENT_NAME);
               URI baseURI = null;
               MediaType contentType = null;
               if (content!=null) {
                  src = content.getAttributeValue("src");
                  String type = content.getAttributeValue("type");
                  if (type!=null) {
                     contentType = MediaType.valueOf(type);
                  }
                  baseURI = content.getBaseURI();
               }
               if (entries!=null) {
                  entries.add(entryId);
               }
               
               Status exists = null;
               try {
                  exists = entryClient.get();
               } catch (Exception ex) {
                  log.log(Level.SEVERE,"Cannot get entry "+entryClient.getLocation()+", error: "+ex.getMessage(),ex);
                  errorCount++;
                  return;
               }
               if (exists.isSuccess()) {
                  entryClient.getEntry().index();
                  try {
                     
                     Status status = entryClient.update(entryDoc);
                     if (!status.isSuccess()) {
                        log.severe("Cannot update entry "+entryClient.getLocation()+", status="+status.getCode());
                        errorCount++;
                     }
                  } catch (Exception ex) {
                     log.log(Level.SEVERE,"Cannot update entry "+entryClient.getLocation()+", error: "+ex.getMessage(),ex);
                     errorCount++;
                  }
                  if (src!=null) {
                     // update media
                     Reference mediaLocation = new Reference(baseURI.resolve(src).toString());
                     Client client = new Client(mediaLocation.getSchemeProtocol());
                     Request getRequest = new Request(Method.GET,mediaLocation);
                     if (source.getIdentity()!=null) {
                        getRequest.setChallengeResponse(new ChallengeResponse(ChallengeScheme.HTTP_BASIC,source.getUsername(),source.getPassword()));
                     }
                     Response getResponse = client.handle(getRequest);
                     if (getResponse.getStatus().isSuccess()) {
                        Reference targetLocation = new Reference(targetClient.getLocation().resolve(src).toString());
                        Client targetClient = new Client(targetLocation.getSchemeProtocol());
                        Request putRequest = new Request(Method.PUT,targetLocation);
                        if (target.getIdentity()!=null) {
                           putRequest.setChallengeResponse(new ChallengeResponse(ChallengeScheme.HTTP_BASIC,target.getUsername(),target.getPassword()));
                        }
                        Representation rep = getResponse.getEntity();
                        if (contentType!=null) {
                           rep.setMediaType(contentType);
                        }
                        putRequest.setEntity(rep);
                        Response putResponse = targetClient.handle(putRequest);
                        if (!putResponse.getStatus().isSuccess()) {
                           log.severe("Cannot put media update to "+targetLocation+", status="+putResponse.getStatus().getCode());
                        }
                     } else {
                        if (!getResponse.getStatus().isSuccess()) {
                           log.severe("Cannot get media from "+mediaLocation+", status="+getResponse.getStatus().getCode());
                        }
                     }
                  }
               } else if (exists.getCode()==Status.CLIENT_ERROR_NOT_FOUND.getCode()) {
                  if (src==null) {
                     // regular entry
                     try {
                        targetClient.createEntry(entryDoc);
                     } catch (StatusException ex) {
                        log.severe("Cannot create entry "+entryClient.getLocation()+", status="+ex.getStatus().getCode());
                        errorCount++;
                     } catch (Exception ex) {
                        log.log(Level.SEVERE,"Cannot create entry "+entryClient.getLocation()+", error: "+ex.getMessage(),ex);
                        errorCount++;
                     }
                  } else {
                     // media entry
                     Reference mediaLocation = new Reference(baseURI.resolve(src).toString());
                     Client client = new Client(mediaLocation.getSchemeProtocol());
                     Request request = new Request(Method.GET,mediaLocation);
                     if (source.getIdentity()!=null) {
                        request.setChallengeResponse(new ChallengeResponse(ChallengeScheme.HTTP_BASIC,source.getUsername(),source.getPassword()));
                     }
                     Response response = client.handle(request);
                     if (response.getStatus().isSuccess()) {
                        try {
                           Representation rep = response.getEntity();
                           if (contentType!=null) {
                              rep.setMediaType(contentType);
                           }
                           Entry mediaEntry = targetClient.createMedia(entryId, src, rep);
                           entryClient.setEntry(mediaEntry);
                           try {
                              Status status = entryClient.update(entryDoc);
                              if (!status.isSuccess()) {
                                 log.severe("Cannot update entry "+entryClient.getLocation()+", status="+status.getCode());
                                 errorCount++;
                              }
                           } catch (Exception ex) {
                              log.log(Level.SEVERE,"Cannot update entry "+entryClient.getLocation()+", error: "+ex.getMessage(),ex);
                              errorCount++;
                           }
                        } catch (StatusException ex) {
                           log.severe("Cannot create media resource "+src+", status="+ex.getStatus().getCode());
                           errorCount++;
                        } catch (Exception ex) {
                           log.log(Level.SEVERE,"Cannot create media resource "+src+", error: "+ex.getMessage(),ex);
                           errorCount++;
                        }
                     } else {
                        log.severe("Cannot get media resource "+mediaLocation+", status="+response.getStatus().getCode());
                        errorCount++;
                     }
                  }
               } else {
                  log.severe("Cannot check entry "+entryClient.getLocation()+", status="+exists.getCode());
                  errorCount++;
               }
            }
            public void onEnd() {
               if (additive || !ok) {
                  return;
               }
               // TODO: remove extra entries
            }
         });
      } catch (Exception ex) {
         log.log(Level.SEVERE,"Error while getting source feed: "+ex.getMessage(),ex);
         errorCount++;
      }
      
   }
   
   static URI toURI(String value)
   {
      int colon = value.indexOf(':');
      if (colon<2) {
         File file = new File(value);
         return file.toURI();
      } else {
         return URI.create(value);
      }
   }
   
   public static void main(String [] args)
   {
      int index = 0;
      String sourceIdentity = null;
      String targetIdentity = null;
      while (index<args.length && args[index].charAt(0)=='-') {
         if (args[index].equals("--source-identity")) {
            index++;
            if (index==args.length) {
               System.err.println("--source-identity requires an argument.");
               return;
            }
            sourceIdentity = args[index];
         } else if (args[index].equals("--target-identity")) {
            index++;
            if (index==args.length) {
               System.err.println("--target-identity requires an argument.");
               return;
            }
            targetIdentity = args[index];
         }
         index++;
      }
      if ((args.length-index)<2) {
         System.err.println("A source and target feed URI is required.");
         return;
      }
      Link source = new Link("source",toURI(args[index]));
      if (sourceIdentity!=null) {
         int colon = sourceIdentity.indexOf(':');
         source.setIdentity(sourceIdentity.substring(0,colon), sourceIdentity.substring(colon+1));
      }
      Link target = new Link("target",toURI(args[index+1]));
      if (targetIdentity!=null) {
         int colon = targetIdentity.indexOf(':');
         target.setIdentity(targetIdentity.substring(0,colon), targetIdentity.substring(colon+1));
      }
      FeedSynchronizer sync = new FeedSynchronizer(source,target);
      sync.run();
      int errorCount = sync.getErrorCount();
      if (errorCount>0) {
         System.err.println("There were "+errorCount+" errors during synchronization.");
      }
      System.exit(errorCount);
   }
}
